
package com.zhike.utils;

import com.eva.epc.common.util.CommonUtils;
import com.zhike.common.vo.GroupMemberCountVO;
import com.zhike.common.vo.UserAvatarVO;
import com.zhike.mapper.group.GroupBaseMapper;
import com.zhike.service.store.Alioss;
import com.zhike.service.system.ISystemConfigService;
import com.zhike.utils.file.CustomMultipartFile;
import com.zhike.utils.file.FileAvatar;
import com.zhike.common.conf.IMBaseConf;
import com.zhike.common.constant.Constants;
import com.zhike.common.exception.ZkimException;
import com.zhike.common.utils.FileHelper;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.liuxp.minioplus.api.StorageService;
import org.liuxp.minioplus.api.model.vo.FileMetadataInfoVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * 群组头像生成辅助类（生成逻辑与微信保持一致：4人以内生成4宫格、9人以内生成9宫格等）。
 * <p>
 * <b>关于性能的说明：</b>
 * 因为生成群头像时，需要读取群员头像文件并写入最络群组头像文件，因而在用户量较大时，
 * 这些磁盘操作都有可能带来性能瓶颈，所以建议如果是性能优先的im系统里可以考虑不要自
 * 动后成群头像，否则请评估并考虑将这样的功能拆分成独立的服务，从而不至于影响主业
 * 务服务器的性能.
 *
 * @author chuan_bai(https : / / www.jianshu.com / p / 394f9b0357b3)
 * @author Jack Jiang
 * @since 4.3
 */
@Slf4j
@Component
public final class GroupAvatarHelper {

  @Resource
  private GroupBaseMapper groupBaseMapper;

  @Resource
  private IMBaseConf baseConf;

  @Autowired
  private FileAvatar fileAvatar;

  @Autowired
  private StorageService storageService;

  @Autowired
  private ISystemConfigService systemConfigService;

  @Autowired
  private Alioss alioss;

  /**
   * 图片的间隙
   */
  private static final int SIDE = 6;
  /**
   * 画板尺寸
   */
  private static final int CANVANS_W = 112;
  private static final int CANVANS_H = 112;

  /**
   * 尺寸1 （小）
   */
  private static final int ONE_IMAGE_SIZE = CANVANS_H - (2 * SIDE);
  /**
   * 尺寸2 （中）
   */
  private static final int TWO_IMAGE_SIZE = (CANVANS_H - (3 * SIDE)) / 2;
  /**
   * 尺寸3 （大）
   */
  private static final int FIVE_IMAGE_SIZE = (CANVANS_H - (4 * SIDE)) / 3;

  private static BufferedImage defaultGroupMemberAvatar = null;

  /**
   * 一站式生成一个群组的群头像（本方法将自动查询该群内的前9个成员头像并拼合出完整群头像的全部逻辑）。
   *
   * @param groupId 要生成群头像的群组id
   * @return true表示生成成功，否则表示过程中出错了（没有成功生成）
   * @see #getCombinationOfhead
   */
  public boolean generateGroupAvatar(String groupId) {
    log.debug("【生成群聊头像-START】群组" + groupId + "的头像生成马上开始。。。。。。");

    long t = System.currentTimeMillis();

    boolean sucess = false;
    try {
      // 当上次拼合群头像时，使用的群员头像数
      GroupMemberCountVO cnts = groupBaseMapper.queryGroupMemberCount(groupId);

      int current_group_member_count = CommonUtils.getIntValue(cnts.getGMemberCount(), 0);
      int last_avatar_include_cnt = CommonUtils.getIntValue(cnts.getAvatarIncludeCnt(), 0);
      // 当当前群成员大于9 且 上次拼合群成员头像时用的头像数已经足9时，就不需要重新
      // 生成群头像了，这样就能节省服务端的资源（不然反复生成群头像，很不经济）
      if (current_group_member_count > 9 && last_avatar_include_cnt >= 9) {
        log.debug("【生成群聊头像-END】群组" + groupId
            + "的头像无需再次生成，原因：（当前群员总数=" + current_group_member_count
            + ", 上次拼合使用头像数=" + last_avatar_include_cnt + "）。");
        return false;
      }

      // 当群不足9人 或上次拼合群成员头像时用的头像数不足9人时，就需
      // 要重新生成群头像，从而保证群头像能与当前实际的群人数保持一致。
      // 总之生成原则就是：当群小于9人或上次拼合群成员头像时用的头像数不足9人时，就重新生新群头像！

      log.debug("【生成群聊头像-continue】群组" + groupId
          + "的头像将继续生成>>> ，原因：（当前群员总数=" + current_group_member_count
          + ", 上次拼合使用头像数=" + last_avatar_include_cnt + "）。");

      // 查询该群中9个用户的头像文件名（不足9个则返回实际数据
      // ，大于9个的群则尽最大可能返回排序后的9个有头像的人）
      List<UserAvatarVO> list = groupBaseMapper.queryGroupUserHeadOrderNine(groupId);

      log.debug("【生成群聊头像-第1/2步】群组" + groupId + "的前9个用户查询完成(实取数量：" + list.size() + "人)，此步共耗时："
          + (System.currentTimeMillis() - t) + "毫秒。");

      if (list.size() > 0) {
        // 开始拼合头像并保存到磁盘
        boolean combinateSucess = this.getCombinationOfhead(list, groupId, 1);

        if (combinateSucess) {
          try {
            // 更新数据中本次拼合头像的人数
            groupBaseMapper.updateGroupAvatarIncludeCnt(list.size(), groupId);
          } catch (Exception e) {
            log.warn(e.getMessage(), e);
          }
        }

        sucess = combinateSucess;
      } else {
        log.warn("【生成群聊头像】群组" + groupId + "的群员数是0，无需生成群头像！");
      }
    } catch (Exception e) {
      log.warn("【群聊头像】生成群" + groupId + "的头像时出错了", e);
    }

    log.debug("【生成群聊头像-END】群组" + groupId + "的头像生成已完成（成功?"
        + sucess + "），两步总耗时共：" + (System.currentTimeMillis() - t) + "毫秒。");

    return sucess;
  }

  /**
   * 获得默认的成员头像（这是在极端情况下读不到真正的头像文件时可以使用）。
   *
   * @return
   */
  private static BufferedImage getDefaultGroupMemberAvatar() {
    if (defaultGroupMemberAvatar == null) {
      try {
        // 缓存起提高性能
        defaultGroupMemberAvatar = ImageIO.read(new File("classpath:/static/images/default_group_member_avatar.png"));
      } catch (Exception e) {
        log.warn(e.getMessage(), e);
      }
    }
    return defaultGroupMemberAvatar;
  }

  private String getPath(String avatarFileName) {
    String path = null;
    if (!CommonUtils.isStringEmpty(avatarFileName, true)) {
      // 头像缩略图文件路径
      String avatarThumbFilePath = baseConf.getDIR_USER_AVATAR_UPLOAD() + "th_" + avatarFileName;
      // 头像文件路径
      String avatarFilePath = baseConf.getDIR_USER_AVATAR_UPLOAD() + avatarFileName;
      // 优先使用头像缩略图来拼合群聊头像，这样能提高服务端性能和减少资源占用，
      // 只在缩略图不存在时使用头像文件原图（不存在缩略图的情况可能是RainbowChat4.3
      // 以前的系统中没有生成用户头像，也可能是用户头像上传时没有成功生成缩略图等）
      if (!new File(avatarThumbFilePath).exists())
        path = avatarFilePath;
      else
        path = avatarThumbFilePath;
    }
    return path;
  }

  /**
   * 用指定的用户头像文件集合，拼合出最终的RainbowChat的群组头像。
   *
   * @param membersAvatarFileNames 群员的头像文件链接
   * @param groupId                群编号
   * @param process                0本地处理 1 minio / aliOSS
   * @return
   * @throws IOException
   */
  private boolean getCombinationOfhead(List<UserAvatarVO> membersAvatarFileNames, String groupId, int process)
      throws Exception {
    log.debug("【生成群聊头像-第2/2步】群组" + groupId + "的群头像拼合马上开始 ....");

    long t = System.currentTimeMillis();

    List<BufferedImage> bufferedImages = new ArrayList<BufferedImage>();

    int imageSize = 0;
    if (membersAvatarFileNames.size() <= 1) {
      // 若为一张图片
      imageSize = ONE_IMAGE_SIZE;
    } else if (membersAvatarFileNames.size() > 1 && membersAvatarFileNames.size() < 5) {
      // 若为2-4张图片

      imageSize = TWO_IMAGE_SIZE;
    } else {
      // 若>=5张图片
      imageSize = FIVE_IMAGE_SIZE;
    }

    for (int i = 0; i < membersAvatarFileNames.size(); i++) {
      // 本次要读取的图片路径
      String path = null;

      switch (process) {
        case 0:
          String avatarFileName = membersAvatarFileNames.get(i).getAvatar();
          path = getPath(avatarFileName);
          break;
        case 1:
          path = fileAvatar.getUserAvatarUrl(membersAvatarFileNames.get(i).getFakeUid());
          log.debug("开始合成头像 {} - {}", i, path);
          break;
        default:
          throw new ZkimException("不支持的存储方式");
      }

      BufferedImage resize2 = GroupAvatarHelper.resize2(path, imageSize, imageSize, true);
      bufferedImages.add(resize2);
    }

    BufferedImage outImage = new BufferedImage(CANVANS_W, CANVANS_H, BufferedImage.TYPE_INT_RGB);

    // 生成画布
    Graphics g = outImage.getGraphics();
    Graphics2D g2d = (Graphics2D) g;
    // 设置背景色
    g2d.setBackground(new Color(219, 223, 225));
    // 通过使用当前绘图表面的背景色进行填充来清除指定的矩形。
    g2d.clearRect(0, 0, CANVANS_W, CANVANS_H);

    // 开始拼凑 根据图片的数量判断该生成那种样式的组合头像目前为九种
    for (int i = 1; i <= bufferedImages.size(); i++) {
      Integer size = bufferedImages.size();

      switch (size) {
        case 1: {
          g2d.drawImage(bufferedImages.get(i - 1), SIDE, SIDE, null);
          break;
        }
        case 2: {
          if (i == 1)
            g2d.drawImage(bufferedImages.get(i - 1), SIDE, (CANVANS_W - imageSize) / 2, null);
          else
            g2d.drawImage(bufferedImages.get(i - 1), 2 * SIDE + imageSize, (CANVANS_W - imageSize) / 2, null);

          break;
        }
        case 3: {
          if (i == 1)
            g2d.drawImage(bufferedImages.get(i - 1), (CANVANS_W - imageSize) / 2, SIDE, null);
          else
            g2d.drawImage(bufferedImages.get(i - 1), (i - 1) * SIDE + (i - 2) * imageSize, imageSize + (2 * SIDE),
                null);

          break;
        }
        case 4: {
          if (i <= 2)
            g2d.drawImage(bufferedImages.get(i - 1), i * SIDE + (i - 1) * imageSize, SIDE, null);
          else
            g2d.drawImage(bufferedImages.get(i - 1), (i - 2) * SIDE + (i - 3) * imageSize, imageSize + 2 * SIDE, null);

          break;
        }
        case 5: {
          if (i <= 2)
            g2d.drawImage(bufferedImages.get(i - 1),
                (CANVANS_W - 2 * imageSize - SIDE) / 2 + (i - 1) * imageSize + (i - 1) * SIDE,
                (CANVANS_W - 2 * imageSize - SIDE) / 2, null);
          else
            g2d.drawImage(bufferedImages.get(i - 1), (i - 2) * SIDE + (i - 3) * imageSize,
                ((CANVANS_W - 2 * imageSize - SIDE) / 2) + imageSize + SIDE, null);

          break;
        }
        case 6: {
          if (i <= 3)
            g2d.drawImage(bufferedImages.get(i - 1), SIDE * i + imageSize * (i - 1),
                (CANVANS_W - 2 * imageSize - SIDE) / 2, null);
          else
            g2d.drawImage(bufferedImages.get(i - 1), ((i - 3) * SIDE) + ((i - 4) * imageSize),
                ((CANVANS_W - 2 * imageSize - SIDE) / 2) + imageSize + SIDE, null);

          break;
        }
        case 7: {
          if (i <= 1)
            g2d.drawImage(bufferedImages.get(i - 1), 2 * SIDE + imageSize, SIDE, null);

          if (i <= 4 && i > 1)
            g2d.drawImage(bufferedImages.get(i - 1), ((i - 1) * SIDE) + ((i - 2) * imageSize), 2 * SIDE + imageSize,
                null);

          if (i <= 7 && i > 4)
            g2d.drawImage(bufferedImages.get(i - 1), ((i - 4) * SIDE) + ((i - 5) * imageSize), 3 * SIDE + 2 * imageSize,
                null);

          break;
        }
        case 8: {
          if (i <= 2)
            g2d.drawImage(bufferedImages.get(i - 1),
                (CANVANS_W - 2 * imageSize - SIDE) / 2 + (i - 1) * imageSize + (i - 1) * SIDE, SIDE, null);

          if (i <= 5 && i > 2)
            g2d.drawImage(bufferedImages.get(i - 1), ((i - 2) * SIDE) + ((i - 3) * imageSize), 2 * SIDE + imageSize,
                null);

          if (i <= 8 && i > 5)
            g2d.drawImage(bufferedImages.get(i - 1), ((i - 5) * SIDE) + ((i - 6) * imageSize), 3 * SIDE + 2 * imageSize,
                null);

          break;
        }
        case 9: {
          if (i <= 3)
            g2d.drawImage(bufferedImages.get(i - 1), (i * SIDE) + ((i - 1) * imageSize), SIDE, null);

          if (i <= 6 && i > 3)
            g2d.drawImage(bufferedImages.get(i - 1), ((i - 3) * SIDE) + ((i - 4) * imageSize), 2 * SIDE + imageSize,
                null);

          if (i <= 9 && i > 6)
            g2d.drawImage(bufferedImages.get(i - 1), ((i - 6) * SIDE) + ((i - 7) * imageSize), 3 * SIDE + 2 * imageSize,
                null);

          break;
        }
        default:
          break;
      }
    }

    // 拼合后的群聊头像保存路径
    StringBuffer outPath = new StringBuffer().append(baseConf.getDIR_USER_AVATARGROUP_DIR())
        .append(groupId).append(".jpg"); // 生成的群组图片名形如”000000001.jpg“
    log.debug("拼合后的头像路径: {}", outPath);

    String format = "JPG";
    File file = new File(outPath.toString());
    if (!file.exists()) {
      file.mkdirs();
    }
    // 保存群头像
    boolean success = ImageIO.write(outImage, format, file);

    if (process == 1) {
      String storage = systemConfigService.getValueByKey("storage", "default");
      String groupAvatar = "";
      if (storage.equals("minio")) {
        FileMetadataInfoVo vo = storageService.createFile(file.getName(),
            false, groupId, new FileInputStream(file));
        if (null != vo) {
          groupAvatar = vo.getFileKey();
        }
      } else if (storage.equals("aliyun")) {
        CustomMultipartFile customMultipartFile = new CustomMultipartFile(file, groupId);
        String groupAvatarUrl = alioss.upload(customMultipartFile, Constants.UPLOAD_GROUP_AVATAR, groupId);
        if (StringUtils.isNotBlank(groupAvatarUrl)) {
          groupAvatar = FileHelper.extractPath(groupAvatarUrl);
        }
      }

      // 更新群头像
      boolean res = groupBaseMapper.updateGroupAvatar(groupAvatar, groupId, System.currentTimeMillis()) > 0;
      log.debug(
          "【群聊头像-第2/2步】群组" + groupId + "的头像拼合完毕(成功了?" + res + ")，此步共耗时：" + (System.currentTimeMillis() - t)
              + "毫秒。");
      return res;

    }
    return success;
  }

  /**
   * 图片缩放。
   *
   * @param filePath 图片路径（支持网络图片）
   * @param height   高度
   * @param width    宽度
   * @param bb       比例不对时是否需要补白
   */
  private static BufferedImage resize2(String filePath, int height, int width, boolean bb) {
    try {
      double ratio = 0; // 缩放比例
      // log.info("图片缩放"+filePath);
      BufferedImage bi = null;

      // 进行容错，保证在读不到图片数据时余下代码还能按照正常算法走完
      if (!CommonUtils.isStringEmpty(filePath, true)) {
        try {
          if (filePath.indexOf("http://") == 0 || filePath.indexOf("https://") == 0)
            bi = ImageIO.read(new URL(filePath));
          else
            bi = ImageIO.read(new File(filePath));
        } catch (Exception e) {
          log.warn(e.getMessage() + "(" + filePath + ")", e);
        }
      }

      // 极端情况下读不到图片数据的话，至少给个默认的
      if (bi == null)
        bi = getDefaultGroupMemberAvatar();

      Image itemp = bi.getScaledInstance(width, height, Image.SCALE_SMOOTH);
      // 计算比例
      if ((bi.getHeight() > height) || (bi.getWidth() > width)) {
        if (bi.getHeight() > bi.getWidth())
          ratio = (new Integer(height)).doubleValue() / bi.getHeight();
        else
          ratio = (new Integer(width)).doubleValue() / bi.getWidth();

        AffineTransformOp op = new AffineTransformOp(AffineTransform.getScaleInstance(ratio, ratio), null);
        itemp = op.filter(bi, null);
      }
      if (bb) {
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = image.createGraphics();
        g.setColor(Color.white);
        g.fillRect(0, 0, width, height);

        if (width == itemp.getWidth(null)) {
          g.drawImage(itemp, 0, (height - itemp.getHeight(null)) / 2,
              itemp.getWidth(null), itemp.getHeight(null),
              Color.white, null);
        } else {
          g.drawImage(itemp, (width - itemp.getWidth(null)) / 2, 0,
              itemp.getWidth(null), itemp.getHeight(null),
              Color.white, null);
        }
        g.dispose();
        itemp = image;
      }
      return (BufferedImage) itemp;
    } catch (Exception e) {
      log.warn(e.getMessage() + "(" + filePath + ")", e);
    }

    return null;
  }

  // /**
  // * 可依次生成九种情况的群头像
  // *
  // * @param args
  // * @throws IOException
  // */
  // public static void main(String[] args) throws Exception
  // {
  // DBShell.DB_TYPE = DBShell.DBType.mySql;
  //
  // String groupId = "0000000155";
  //
  //// List<String> list = new ArrayList<String>();
  //// list.add("400070_79196464a7a8bd4103eff494e0d4cd67.jpg");
  //// list.add("a.jpg");
  //// list.add("400092_4f330a9bcf1d22054364c119ceb8b753.jpg");
  //// list.add(null);
  //// list.add("400088_1d88c9fea46d09692828b5251f420db0.jpg");
  //// list.add("");
  //// list.add("400096_76dfc2ab1f57af24a495b25dce1395dc.jpg");
  //// list.add("400095_c2ea8d7a74a96b329b5d84cd21208f9d.jpg");
  //// GroupAvatarHelper.getCombinationOfhead(list, groupId.toString());
  //
  // GroupAvatarHelper.generateGroupAvatar(groupId);
  // }
}
